This software source package is Copyright ⌐ 1990 by MPH╩╤╩All Rights Reserved. This package is hereby placed in the public domain. It may be freely distributed in source or object code format; however, the source code may not be sold for profit or charged for in any way. The source code must be distributed as a package including all H files, sample code and projects and this documentation.
The Task Manager is a package for creating and managing tasks╩╤╩separate execution threads that run non-preemptively in the background. Tasks should have low stack requirements, and should periodically call a Task Manager idling routine to allow other tasks to run. Tasks are ideal for lengthy processes that you would like to have run in the background, since the task runs in a separate execution thread from your event loop.
The Task Manager was written using THINK C 4.0. It requires the ANSI and MacTraps libraries, and the Gestalt function.
Overview
The Event Loop
A simplified Macintosh event loop looks something like this:
The application calls WaitNextEvent to retrieve the next event from the event queue. The application-defined routine GetSleep determines the minimum number of ticks to allow the operating system to wait before returning control to this application. When a null event occurs, the application calls its DoIdle routine to perform background operations. This is when the task manager should be called to run background tasks:
void DoIdle( void )
{
RunTasks( GetWake());
}
At idle time, the application calls the Task Manager routine RunTasks to process background tasks. The single parameter to RunTasks is the number of minimum number of ticks that background tasks may use. This time is calculated by the application-defined routine, GetWake.
The Task
The task itself is an application-defined routine that may look something like this:
void MyTaskProc( long taskRefCon )
{
Boolean moreToDo;
moreToDo = TRUE;
while( moreToDo ) {
/* Allow other tasks to run */
TaskIdle();
/* Do my task processing */
╔
}
}
The task procedure should periodically call the Task Manager routine TaskIdle, which allows the Task Manager to run other tasks. When TaskIdle is called, the Task Manager checks whether the number of ticks specified in the call to RunTasks has been exceeded, or if there are any events pending for the application. If there is no reason to suspend the task, then TaskIdle simply returns. Otherwise, the task is suspended and control returns to DoIdle, immediately after the call to RunTasks. The application can then run its event loop. The next time the application calls RunTasks, control will return to MyTaskProc immediately following the call to TaskIdle.
Sleep and Wake Times
The application spends its time alternating between periods of sleep and wake. It does its work while awake, and other applications do their work while it is asleep. You must choose a good balance of sleep and wake times so that your application can get its work done without too much expense from other applications.
There are two factors that should affect your application╒s sleep-wake cycle: whether the application is in the foreground or background, and whether the application has work to do at the moment. This table shows the four resulting situations and gives sample values for wake and sleep (all values are in ticks):
in Foreground
in Background
has Tasks high wake (60)
low sleep (0) low wake (30)
medium sleep (30)
no Tasks high wake (60)
high sleep (12000) low wake (30)
high sleep (12000)
There are a few things to note here:
Ñ When there are no tasks, we sleep for a ridiculously long time. The operating system will wake us when we receive an event. Otherwise, there╒s nothing to do, so we let other applications run.
Ñ However, there╒s no need to adjust our wake time based on number of tasks, since RunTasks will return immediately when there are no tasks.
Ñ We presume that the user wants the foreground application to run a little faster than applications in the background. When we are in the foreground and busy, we work for one second, then poll for events and allow other applications to work briefly.
Task Manager Routines
Task Manager Initialization
Before using the Task Manager routines, your application should call InitTasking to set up the Task Manager╒s data structures:
OSErr InitTasking( void );
If the Task Manager could not be initialized due to memory limitations, InitTasking will return the appropriate system error code.
Creating Tasks
To create a new task, call NewTask:
OSErr NewTask( TaskProcPtr taskProc, long taskRefCon, int *taskRefNum );
where
taskProc is the task procedure
taskRefCon is a parameter that is passed to the task
taskRefNum is the task╒s reference number. You may pass a NIL pointer if you don╒t need the reference number.
NewTask returns a Memory Manager error code if there is not enough memory available for the task╒s data structures or stack. The task╒s stack will be allocated from temporary memory if it is available.
A TaskProcPtr is defined as:
typedef void TaskProc( long taskRefCon );
typedef TaskProc *TaskProcPtr;
The task procedure can either be a static or an extern function, but it╒s CODE segment should remain loaded until the task procedure returns.
Disposing of Tasks
When a task has finished it╒s work, its task procedure simply returns. It will be automatically removed. To dispose of a task before its task procedure returns (for example, if the user selects ╥Quit╙), call DisposeTask:
OSErr DisposeTask( int taskRefNum );
where
taskRefNum is the task╒s reference number.
DisposeTask returns an error code (paramErr) if the task reference number is invalid.
Managing Task Stacks
A task╒s stack size is 8K by default. You can create a task with a different stack size by calling SetStackSize before calling NewTask. The format for SetStackSize is:
Size SetStackSize( Size stackSize );
where
stackSize is the size that you want NewTask to use when allocating stacks.
SetStackSize returns the previous stack size. You can use the constant DefaultStackSize to refer to the Task Manager╒s stack size default.
To help determine how large a stack should be, you can call SetStackChecking after you call InitTasking. If stack checking is enabled, then the Task Manager will break to the debugger when a task procedure returns, and it will indicate how much stack space was actually used. The format for SetStackChecking is:
void SetStackChecking( Boolean checking );
where
checking is TRUE if you want to enable stack checking, and FALSE if you want to disable stack checking.
The Task Manager will also break to the debugger if it detects that a task╒s stack has been overrun.
Within your task procedure, you may wish to call the Memory Manager routine StackSpace to determine how much room is available on your stack. You can use this information to determine how deeply you wish to nest a recursive call, for example.
Letting Tasks Run
Your application should call RunTasks in response to a null event or an error return code from GetNextEvent or WaitNextEvent:
void RunTasks( unsigned long wakeTime );
where
wakeTime is the amount of time you want allocated to background tasks.
The constant DefaultWakeTime is a good choice for the wakeTime parameter.
Occasionally, your task procedure should call TaskIdle so that it may allow other applications and other tasks in your application to run.
void TaskIdle( void );
TaskIdle will return control to the application (that is, the application will regain control after it╒s call to RunTasks) if there are no more tasks to run, if the wake time specified in RunTasks has elapsed, or if there is an event pending for the application.
Keeping Track of Tasks
Your application can keep track of its tasks by calling these Task Manager routines:
int CountTasks( void );
CountTasks returns the number of tasks that the Task Manager is currently running.
int CurrentTask( void );
CurrentTask returns the task reference number of the current task, or 0 if there is no current task (if called from the application, for example).
int GetIndTask( int index );
where
index is the number of the nth task counting from 0 to CountTasks() - 1
GetIndTask returns the task reference number of the given task, or 0 if index is invalid.
long GetTaskRefCon( int taskRefNum );
where
taskRefNum is the reference number of the task whose reference constant you want
GetTaskRefCon returns the reference constant for the given task. This is useful if you want to send messages to your tasks by changing parameters that are pointed to by the tasks╒ reference constants.
Limitations
Task Procedures
You are only limited by memory to the number of tasks you can create simultaneously.
Task procedures can call any Toolbox routine (including QuickDraw). However, the current grafPort is not preserved across calls to TaskIdle. If you have several tasks drawing to different ports, or if you draw in your application code, you may need to call SetPort after calling TaskIdle.
ANSI
The Task Manager uses the setjmp and longjmp routines from the THINK C ANSI library. If you don╒t want to include ANSI, then include the file setjmp.c in your project.
Profiler
Task.c will not work if it is compiled with the Profiler code generation option. To circumvent this, create a project (Task.╣) containing only Task.c. Turn the Profiler code generation option off and compile Task.c. In your application╒s project, remove Task.c and replace it with Task.╣.
You should also avoid profiling across TaskIdle calls. Set the global variable _profile to FALSE before calling TaskIdle, then set it back to TRUE afterwards.
Yours Truly
I welcome any comments or suggestions that will help me improve or extend the functionality of the Task Manager. You can reach me at: